//【案例开放058】1105上机编程[图片表格内容识别转换]学习交流
// Image table content recognition conversion
//日期：2021-11-12 17:35浏览：14732回复：342026-11-12
//失效最近编辑时间：2021-11-12 17:37
// 1.   题目
//今天学习1105专业级第二题。题目：
//题目描述
//华为云推出了“通用表格识别”服务，可以将图片表格转换成文本数据。请你把文本数据进一步转换为「文本型表格」，
//如下图所示：https://oj.rnd.huawei.com/public/upload/9ee6c841ae.jpg
//现给出一个图片表格的文本数据：
//·     每行数据形如 line3 col1 A，表示第3行第1列的单元格的内容为 A
//·     表格的行和列都从 1 开始；文本数据中行、列的最大值分别为表格的行、列数。
//·     内容为由英文字符A-Z组成的字符串，或为空；未描述的单元格内容为空。
//请按如下格式要求，将文本数据转换成「文本型表格」：
//·
//表格第一行和最后一行用字母+和字母-组成，+代表单元格的间隔，-代表一个字符的占位符；
//·     数据行：
//·     用字符|标识单元格的左右边界。
//·     某一列的宽度是该列中所有单元格最长内容长度 + 2（即左右各补充
// 1个空格），该列中内容不足该宽度的用空格补充。 ·
//表格中单元格的内容要求居中对齐，对齐要求：
// 1）尽量使得文本左右两侧的空格数量相等。
// 2）如果文本左右两侧的空格数量无法相等，则左侧比右侧少一个，如图中内容为 CN
// 的单元格。 输入 第一行一个整数 num，表示文本数据的行数，范围 [1,20]。 接下来
// num 行字符串，每行格式如line2 col3 LMN描述一个单元格： ·     line
//是行的固定前缀，行取值范围 [1,100] ·     col 是列的固定前缀，列取值范围
//[1,100] ·     内容为仅由英文字符A-Z组成的字符串，或为空，长度范围 [0,10]。
//输出
//字符串表示的「文本型表格」
//样例
//输入样例 1
// 8
// line1 col3 CN
// line1 col1 A
// line1 col4 D
// line3 col1 X
// line2 col1
// line2 col2 BE
// line2 col3 LMN
// line2 col4
//输出样例 1
//+---+----+-----+---+
//| A |  | CN  | D |
//|   | BE | LMN |   |
//| X |  |   |   |
//+---+----+-----+---+
//提示样例 1
//表格的行列数：
// 1）输入数据最大行为3，因此行数为 3 。
// 2）输入数据最大列为4，因此列数为 4 。
//表格的各列宽度：
//·     第1列：内容最长为1，加2，该列宽度为3 。
//·     第2列：内容最长为2，加2，该列宽度为4 。
//·     第3列：内容最长为3，加2，该列宽度为5
//。该列中内容LMN最长，左右各补充一个空格；CN左侧补充一个空格，右侧补充两个空格。
//·     第4列：内容最长为1，加2，该列宽度为3 。
//输入样例 2
// 2
// line2 col2 A
// line2 col3
//输出样例 2
//+--+---+--+
//|  |   |  |
//|  | A |  |
//+--+---+--+
//提示样例 2
//·
//从输入得到数据的行列数分别为2和3；第一行、第一列、第三列即使内容为空，也需要输出。
//其中第一列内容都为空，列宽度为2（该列中所有单元格最长内容长度 +2）。
// 2.   题目分析
//题目理解：
//题目给出一个表格的文字描述作为输入，要求按照指定格式打印输出表格。
//需要完成的处理有：
// l  解析每一行字符串，得到每个单元格的文本内容
// l  因为输入没有直接给出最大行数和列数，所以需要解析得到最大的行数、列数
// l  得到每一列的最大宽度
//注意点：
// l  某一列的宽度是该列中所有单元格最长内容长度 +
// 2，当整列的单元格都是空的时候，最长内容长度是2 l
// 单元格内要求居中对齐，如左右两个空格无法相等，则靠左、右边多一个空格 l
// 输入未描述的单元格文本为空 解题思路： 可以分为两大步骤： l
// 构造数据：解析输入字符串，得到所有单元格的文本，同时记录每一列的最大宽度
//由于行和列的最大取值是100、每个单元格内的字符串长度最多10，所以可以直接用数组来存储表格内容和每列列宽。
//如果数据规模很大（例如超过100万），就要考虑用hash来存储了。
//在记录列宽时，直接用len(单元格文本)+2，不用再加上边框字符所占的宽度。
// l  显示数据：逐行打印输出每个单元格
//打印输出时通常有两种做法：
// l  做法1：先填充到数组中，再print整个数组
// l  做法2：直接print每一行
//居中对齐时，用下面这个计算方法是比较简便的：
//    int leftFill = (colLen[i] - stringLen) / 2;  // 左边要填充的空格数
//    int rightFill = colLen[i] - stringLen - leftFill; // 右边要填充的空格数
//以样例1的第1行第3列为例：“| CN  |”，leftFill=(5-2)/2=1，rightFill=5-2-1=2
//员工2-C++代码：83行、最大圈复杂度5
#include <algorithm>
#include <iostream>
#include <map>
#include <regex>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
void SplitString(const string& input, char sperChar, vector<string>& outArray);
struct Key {
  unsigned long m_row = 0;
  unsigned long m_col = 0;
};
bool operator<(Key const& k1, Key const& k2) {
  if (k1.m_col != k2.m_col) {
    return k1.m_col < k2.m_col;
  }
  return k1.m_row < k2.m_row;
}
using ColKey = unsigned long;
class Solution {
 public:
  // 待实现函数，在此函数中填入答题代码;
  void TransformationTable(const vector<string>& tableInfo) {
    Parse(tableInfo);
    TextOut();
  }
  void Parse(const vector<string>& tableInfo) {
    std::for_each(tableInfo.begin(), tableInfo.end(), [this](auto& info) {
      std::regex reg("line(\\w+)[ \t]+col(\\w+)[ \t]*(\\w*)");
      std::smatch smatchs;
      if (std::regex_match(info, smatchs, reg)) {
        enum NUM { ONE = 1, TWO = 2, THREE = 3 };
        auto row = std::stoul(smatchs[ONE]);
        auto col = std::stoul(smatchs[TWO]);
        auto& value = smatchs[THREE];
        SetValue(row, col, value);
        SetWidth(col, value);
        m_maxLine = m_maxLine < row ? row : m_maxLine;
        m_maxCol = m_maxCol < col ? col : m_maxCol;
      }
    });
  }
  void SetValue(unsigned long row, unsigned long col,
                std::string const& value) {
    m_values[Key{row, col}] = value;
  }
  void SetWidth(unsigned long col, std::string const& value) {
    auto& width = m_colWidth[col];
    if (width < value.size()) {
      width = value.size();
    }
  }
  void TextOut() const {
    PrintLine();
    for (unsigned int row = 1; row <= m_maxLine; ++row) {
      std::string line;
      for (unsigned int col = 1; col <= m_maxCol; ++col) {
        auto it = m_colWidth.find(col);
        auto len = it == m_colWidth.end() ? 0 : it->second;
        line += "|";
        auto nlen = len + 2;
        auto vit = m_values.find(Key{row, col});
        std::string value = vit == m_values.end() ? "" : vit->second;
        auto vlen = value.size();
        line += std::string((nlen - vlen) / 2, ' ');
        line += value;
        line += std::string(nlen - vlen - (nlen - vlen) / 2, ' ');
      }
      line += "|\n";
      std::cout << line;
    }
    PrintLine();
  }
  void PrintLine() const {
    std::string line;
    for (unsigned int col = 1; col <= m_maxCol; ++col) {
      line += "+";
      auto it = m_colWidth.find(col);
      auto len = it == m_colWidth.end() ? 0 : it->second;
      line += std::string(len + 2, '-');
    }
    line += "+\n";
    std::cout << line;
  }

 private:
  std::map<ColKey, std::size_t> m_colWidth;
  std::map<Key, std::string> m_values;
  unsigned long m_maxLine = 0;
  unsigned long m_maxCol = 0;
};
// 以下为考题输入输出框架，此部分代码不建议改动
void SplitString(const string& input, char sperChar, vector<string>& outArray) {
  stringstream sstr(input);
  string token;
  while (getline(sstr, token, sperChar)) {
    outArray.push_back(token);
  }
}
inline int ReadInt() {
  int number;
  cin >> number;
  return number;
}
inline string ReadLine() {
  string line;
  getline(cin, line);
  return line;
}
inline vector<string> ReadLines(int size) {
  vector<string> lines(size);
  for (int i = 0; i < size; ++i) {
    lines[i] = ReadLine();
  }
  return lines;
}
int main() {
  int lines = ReadInt();
  cin.ignore();
  vector<string> tableInfo = ReadLines(lines);
  Solution solu;
  solu.TransformationTable(tableInfo);
  return 0;
}
//该解法独特之处在于解析输入字符串时，用了正则表达式。
// std::regex reg("line(\\w+)[ \t]+col(\\w+)[ \t]*(\\w*)");
//  std::map<ColKey, std::size_t> m_colWidth; 保存每一列的最大列宽
//  std::map<Key, std::string> m_values; 保存每一个单元格的字符串内容
//函数PrintLine的作用是打印首行和末行。
// std::string(len + 2, '-');
// 作用是产生重复的字符，类似Java中的String.repeat()函数。
//如果不用正则，可以用框架代码提供的SplitString函数来完成字符串的拆分。
//函数拆分合理，圈复杂度不高，可以优化的地方：
// l
// Key为自定义结构体，也可以用C++标准库自带的pair来作为key，这样可以少写一些代码。
// l  很多地方写的还不够简洁，例如第101行：auto it =
// m_colWidth.find(col);实际上不用查找，直接用[]访问，如果找不到会自动插入新的元素并返回。
// C++标准库中map的[]运算符在元素不存在时会插入返回默认值：整数返回0、string返回””
//函数SetWidth及其调用的地方，可以用一行代码实现：m_colWidth[col] =
// max(m_colWidth[col], value.size());
